﻿using System;
using System.Collections.Generic;
using Microsoft.Ccr.Core;

namespace Bettzueche_CCR.Benchmarks {
    /// <summary>
    /// Asynchronous Programming Model mit CCR.
    /// Der Test:
    /// <para>
    /// Es werden N asynchrone Methodenaufrufe getätigt. Jeder Aufruf beinhaltet ein Cast von double zu int
    /// und 100.000 Additionen. Die Ergebnisse werden zusätzlich aufsummiert.
    /// Der Funktionalität von APM (mit Callbacks) ist durch IterativeTask nachgestellt.
    /// </para>
    /// </summary>
    class CCR_APM : CallBackTester {


        /// <summary>
        /// Default Instanz mit Repitions=1000 und LogFile="APM_Benchmark.log" im
        /// Assembly-Pfad.
        /// </summary>
        public CCR_APM() {
            this.Repititions = 1000;
            this.LogFile = "APM_Benchmark.log";
            this.computing = false;
        }

        /// <summary>
        /// Default Instanz mit Repitions=1000 und LogFile="APM_Benchmark.log" im
        /// Assembly-Pfad.
        /// </summary>
        public CCR_APM(int repetitions) {
            this.Repititions = repetitions;
            this.LogFile = "APM_Benchmark.log";
        }

        /// <summary>
        /// Castet den Input zu int und addiert 100.000 mal abwechselnd +/-1.
        /// </summary>
        /// <param name="input">nach int zu castende Zahl</param>
        /// <returns>(int)input, falls Repititions gerade, (int)input -1 sonst.</returns>
        int asyncComputation(double input) {
            int result = (int)input;
            //doing havy computation ;-)
            for (int i = 0; i < 100000; i++) {
                result += (int)Math.Pow(-1.0, i);
            }
            return result;
        }

        /// <summary>
        /// Iterative Handler mit Schema F zur Benutzung asynchroner Delegaten
        /// (post(IAsyncResult) anstelle des Callbacks)
        /// </summary>
        /// <param name="port">dbl-Port mit Werten zur async Berechnung</param>
        /// <returns></returns>
        IEnumerator<ITask> iterativeComputeHandler2(Port<double> port) {
            int totalSum = 0;
            var resPort = new Port<IAsyncResult>();
            asyncCompute aDelelgate = asyncComputation;
            for (int i = 0; i < this.Repititions; i++) {
                yield return port.Receive();

                aDelelgate.BeginInvoke((double)port, resPort.Post, null);
                yield return resPort.Receive();
                //---- Callback ----------
                //IAsyncResult ar = (IAsyncResult)resPort;
                IAsyncResult ar = null;
                if (resPort.Test(out ar))
                    totalSum += aDelelgate.EndInvoke(ar);
            }
            _endTime = DateTime.Now;
            Utilities.WriteLog(LogFile, Repititions, _startTime, (_endTime - _startTime).TotalMilliseconds, "CCR-APM (IterativeTask)");
            computing = false;
            yield break;
        }


        /// <summary>
        /// Iterative Handler mit internem portSet(double,IAsyncResult) und Choice anstelle von Receive.
        /// </summary>
        /// <param name="port">dbl-Port mit Werten zur async Berechnung</param>
        /// <returns>Task</returns>
        IEnumerator<ITask> iterativeComputeHandler3(Port<double> port) {
            int totalSum = 0;
            var resPort = new Port<IAsyncResult>();
            var portSet = new PortSet<double,IAsyncResult>(port,resPort);
            asyncCompute aDelelgate = asyncComputation;
            for (int i = 0; i < this.Repititions; i++) {
                yield return Arbiter.Choice(
                    portSet,
                    dbl => aDelelgate.BeginInvoke(dbl, portSet.Post, null),
                    ar => totalSum += aDelelgate.EndInvoke(ar)
                );
            }
            _endTime = DateTime.Now;
            Utilities.WriteLog(LogFile, Repititions, _startTime, (_endTime - _startTime).TotalMilliseconds, "CCR-APM (IterativeTask)");
            computing = false;
            yield break;
        }


        /// <summary>
        /// ItaratorHandler, der die asynchronen Berechnungen von APM mit CCR-Methoden nachbildet.
        /// </summary>
        /// <param name="port"></param>
        /// <returns></returns>
        IEnumerator<ITask> iterativeComputeHandler(Port<double> port) {
            Port<int> resPort = new Port<int>();
            int result;
            int totalSum = 0;
            for (int i = 0; i < this.Repititions; i++) {
                yield return port.Receive();
                //-------------- asyncMethod ------
                result = (int)((double)port);
                for (int j = 0; j < 100000; j++) {
                    result += (int)Math.Pow(-1.0, j);
                }
                resPort.Post(result);
                yield return resPort.Receive();
                //-------------- callback ---------
                totalSum += resPort;
            }
            _endTime = DateTime.Now;
            //----------- Logging --------------
            port.Post((_endTime - _startTime).TotalMilliseconds);
            yield return port.Receive();
            Utilities.WriteLog(this.LogFile, this.Repititions, this._startTime, (double)port, "CCR-APM (IterativeTask old)");
            computing = false;
        }


        /// <summary>
        /// Startet vier CCR-Tests. Alle Tests bilden die Funktionalität des APM-Tests nach:
        /// <para>
        /// N asynchrone Aufrufe mit jeweils einem cast, 100 000 Additionen. Die Ergebnisse werden aufsummiert 
        /// </para>
        /// <para>
        /// Test1: Iterative Task old</para><para>
        /// Benutzt einen internen Port für die Zwischenergebnisse. Nur bedingt parallelisierte Ausführung !?
        /// </para>
        /// <para>
        /// Test2: Iterative Task new</para><para>
        /// Benutzt den Delegaten wie in APM-Test. Statt Calbback, wird ein interner Port übergeben! Dies
        /// ist die Standardmethode zur Benutzung asnc Methoden in CCR.
        /// </para>
        /// <para>
        /// Test3: Interleave</para><para>
        /// Die async Berechnung (100Tsd Additioen) wird parallel ausgeführt, das Aufsummieren exclusiv auf einer globalen Variable.
        /// </para>
        /// <para>
        /// Test4: MultipleItemReceive + Receive</para><para>
        /// Zwei Arbiter werden benutzt. Der Receive-Handler führt die Addition aus, MultipleItemReceiver sammelt N
        /// Ergebnisse des Receivers, um sie dann aufzusummieren.
        /// </para>
        /// </summary>
        internal void Start() {
            var dispatcher = new Dispatcher(0, "ThreadPool");
            var queue = new DispatcherQueue("taskQueue", dispatcher);
            Port<double> dblPort = new Port<double>();

            #region Iterative Old
            Arbiter.Activate(
                queue,
                new IterativeTask(() => iterativeComputeHandler(dblPort))
            );
            computing = true;
            _startTime = DateTime.Now;
            for (int i = 0; i < this.Repititions; i++) {
                dblPort.Post(3.14);
            }
            this.doSomeStuff();
            Console.WriteLine("  End of Iterative Task 1 (old)");
            #endregion

            #region Iterative New
            dblPort = new Port<double>();
            Arbiter.Activate(
                queue,
                new IterativeTask(() => iterativeComputeHandler2(dblPort))
            );
            computing = true;
            _startTime = DateTime.Now;
            for (int i = 0; i < this.Repititions; i++) {
                dblPort.Post(3.14);
            }
            this.doSomeStuff();
            Console.WriteLine("  End of Iterative Task 2 (new)");
            #endregion

            #region Interleave
            int totalSum = 0;
            int count = 0;
            var sumPort = new Port<int>();
            var endPort = new Port<bool>();
            Arbiter.Activate(
                queue,
                Arbiter.Interleave(
                    new TeardownReceiverGroup(
                        Arbiter.Receive<bool>(false, endPort, (b) =>
                        {
                            _endTime = DateTime.Now;
                            TimeSpan time = _endTime - _startTime;
                            Utilities.WriteLog(LogFile, Repititions, _startTime, time.TotalMilliseconds, "CCR-APM (Interleave)");
                            computing = false;
                        })
                    ),
                    new ExclusiveReceiverGroup(
                        Arbiter.Receive<int>(true, sumPort, (sum) =>
                        {
                            totalSum += sum;
                            if (++count == Repititions)
                                endPort.Post(true);
                        })
                    ),
                    new ConcurrentReceiverGroup(
                        Arbiter.Receive<double>(true, dblPort, (dbl) =>
                        {
                            int result = (int)dbl;
                            for (int j = 0; j < 100000; j++)
                                result += (int)Math.Pow(-1.0, j);
                            sumPort.Post(result);
                        })
                    )
                )
            );
            computing = true;
            _startTime = DateTime.Now;
            for (int i = 0; i < this.Repititions; i++) {
                dblPort.Post(3.14);
            }
            this.doSomeStuff();
            Console.WriteLine("  End of Interleave");
            #endregion

            #region MultipleItemReceive
            var dblPort2 = new Port<double>();
            var sumPort2 = new Port<int>();
            totalSum = 0;
            Arbiter.Activate(
                queue,
                Arbiter.Receive<double>(true, dblPort2, dbl =>
                {
                    int result = (int)dbl;
                    for (int j = 0; j < 100000; j++)
                        result += (int)Math.Pow(-1.0, j);
                    sumPort2.Post(result);
                }),
                Arbiter.MultipleItemReceive<int>(false, sumPort2, Repititions, (ints) =>
                {
                    foreach (int num in ints)
                        totalSum += num;
                    _endTime = DateTime.Now;
                    TimeSpan time = _endTime - _startTime;
                    Utilities.WriteLog(LogFile, Repititions, _startTime, time.TotalMilliseconds, "CCR-APM (MultipleItemRcv)");
                    computing = false;
                })
            );
            computing = true;
            _startTime = DateTime.Now;
            for (int i = 0; i < this.Repititions; i++) {
                dblPort2.Post(3.14);
            }
            this.doSomeStuff();
            Console.WriteLine("  End of Receive/MultipleItemRceive");
            #endregion

            Console.WriteLine("--- End of CCR_APM.Start() ---");
            dispatcher.Dispose();
        }
    }
}
